package org.geektimes.web.mvc;

import org.apache.commons.lang.StringUtils;
import org.geektimes.context.ClassicComponentContext;
import org.geektimes.web.mvc.controller.Controller;
import org.geektimes.web.mvc.controller.PageController;

import javax.annotation.Resource;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.HttpMethod;
import javax.ws.rs.Path;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.logging.Logger;
import java.util.stream.Stream;

import static java.util.Arrays.asList;
import static org.apache.commons.lang.StringUtils.substringAfter;

public class FrontControllerServlet extends HttpServlet {

    private static final Logger log = Logger.getLogger(FrontControllerServlet.class.getName());

    /**
     * 请求路径和 Controller 的映射关系缓存
     */
    private final Map<String, Controller> controllersMapping = new HashMap<>();

    /**
     * 请求路径和 {@link HandlerMethodInfo} 映射关系缓存
     */
    private final Map<String, List<HandlerMethodInfo>> handleMethodInfoMapping = new HashMap<>();

    /**
     * 初始化 Servlet
     */
    @Override
    public void init(ServletConfig servletConfig) {
        //初始化Path请求
        initHandleMethods();
        //Controller注入
        injectControllers();
        //ConfigProperties注入
        injectConfigProperties();
    }

    /**
     * ConfigProperties注入
     */
    private void injectConfigProperties() {
        controllersMapping.values().forEach(c -> {
//            Class<?> controllerClass = c.getClass();
//            Stream.of(controllerClass.getDeclaredFields())
//                    .filter(field -> {
//                        int mods = field.getModifiers();
//                        return !Modifier.isStatic(mods) &&
//                                field.isAnnotationPresent(ConfigProperty.class);
//                    }).forEach(field -> {
//                ConfigProperty configProperty = field.getAnnotation(ConfigProperty.class);
//                String configName = configProperty.name();
//                Config config = ConfigProvider.getConfig();
//                Class fieldClass = (Class)field.getGenericType();
//                Object injectedValue = config.getValue(configName, fieldClass);
//                if(null == injectedValue){
//                    String defaultValue = configProperty.defaultValue();
//                    if(StringUtils.isNotBlank(defaultValue)){
//                        Optional<? extends Converter<? extends Type>> converter = config.getConverter(fieldClass);
//                        if(!converter.isPresent()){
//                            throw new RuntimeException("No Converter registered for class " + field.getClass());
//                        }
//                        injectedValue = converter.get().convert(defaultValue);
//                    }
//                }
//                field.setAccessible(true);
//                try {
//                    // 注入配置值
//                    field.set(c, injectedValue);
//                } catch (IllegalAccessException ignored) {
//                }
//            });
        });
    }

    /**
     * Controller注入
     */
    private void injectControllers() {
        controllersMapping.values().forEach(c -> {
            Class<?> controllerClass = c.getClass();
            Stream.of(controllerClass.getDeclaredFields())
                    .filter(field -> {
                        int mods = field.getModifiers();
                        return !Modifier.isStatic(mods) &&
                                field.isAnnotationPresent(Resource.class);
                    }).forEach(field -> {
                Resource resource = field.getAnnotation(Resource.class);
                String resourceName = resource.name();
                Object injectedObject = ClassicComponentContext.getInstance().getComponent(resourceName);
                field.setAccessible(true);
                try {
                    // 注入目标对象
                    field.set(c, injectedObject);
                } catch (IllegalAccessException ignored) {
                }
            });
        });
    }

    /**
     * 读取所有的 RestController 的注解元信息 @Path
     * 利用 ServiceLoader 技术（Java SPI）
     */
    private void initHandleMethods() {
        for (Controller controller : ServiceLoader.load(Controller.class)) {
            Class<?> controllerClass = controller.getClass();
            Path pathFromClass = controllerClass.getAnnotation(Path.class);
            if(null == pathFromClass){
                //如果SPI的bean未标记Path注解，丢弃该Controller
                log.warning("未标记@Path注解，丢弃该Controller["+controllerClass.getName()+"]注入");
                continue;
            }
            String requestPath = formatPath(pathFromClass.value());
            Method[] publicMethods = controllerClass.getDeclaredMethods();
            // 处理方法支持的 HTTP 方法集合
            for (Method method : publicMethods) {
                Path pathFromMethod = method.getAnnotation(Path.class);
                if(null == pathFromMethod){
                    log.warning("未标记@Path注解，丢弃Method["+controllerClass.getName()+"."+method.getName()+"]注入");
                    continue;
                }
                Set<String> supportedHttpMethods = findSupportedHttpMethods(method);
                if (StringUtils.isNotBlank(pathFromMethod.value())) {
                    requestPath += formatPath(pathFromMethod.value());
                }
                putRequestPathToMethodMapping(requestPath, new HandlerMethodInfo(requestPath, method, supportedHttpMethods));
            }
            controllersMapping.put(requestPath, controller);
        }
    }

    /**
     * 将扫描到的Path方法加入到handleMethodInfoMapping
     * @param requestPath
     *      请求地址
     * @param methodInfo
     *      {@link HandlerMethodInfo}
     */
    private void putRequestPathToMethodMapping(String requestPath, HandlerMethodInfo methodInfo){
        List<HandlerMethodInfo> methodInfos = handleMethodInfoMapping.get(requestPath);
        if(null == methodInfos){
            methodInfos = new ArrayList<>();
        }
        methodInfos.add(methodInfo);
        handleMethodInfoMapping.put(requestPath, methodInfos);
    }

    /**
     * 格式化路径
     * @return
     *      以"/"为开头的路径
     */
    private String formatPath(String path){
        String lstr = "/";
        //2个斜杠就去掉一个
        path = StringUtils.replace(path, "//", lstr);
        //必须以斜杠开头
        if(!path.startsWith(lstr)){
            path = lstr + path;
        }
        //最后面不要有斜杠
        if(path.endsWith(lstr)){
            path = StringUtils.substring(path, 0, path.length()-1);
        }
        return path;
    }

    /**
     * 获取处理方法中标注的 HTTP方法集合
     * @param method 处理方法
     */
    private Set<String> findSupportedHttpMethods(Method method) {
        Set<String> supportedHttpMethods = new LinkedHashSet<>();
        for (Annotation annotationFromMethod : method.getAnnotations()) {
            HttpMethod httpMethod = annotationFromMethod.annotationType().getAnnotation(HttpMethod.class);
            if (httpMethod != null) {
                supportedHttpMethods.add(httpMethod.value());
            }
        }

        if (supportedHttpMethods.isEmpty()) {
            supportedHttpMethods.addAll(asList(HttpMethod.GET, HttpMethod.POST,
                    HttpMethod.PUT, HttpMethod.DELETE, HttpMethod.HEAD, HttpMethod.OPTIONS));
        }
        return supportedHttpMethods;
    }

    /**
     * 请求入口方法
     */
    @Override
    public void service(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // 建立映射关系
        // requestURI = /a/hello/world
        String requestUri = formatPath(request.getRequestURI());
        // contextPath  = /a or "/" or ""
        String prefixPath = request.getContextPath();
        // 映射路径（子路径）
        String requestMappingPath = substringAfter(requestUri,
                StringUtils.replace(prefixPath, "//", "/"));
        // 映射到 Controller
        Controller controller = controllersMapping.get(requestMappingPath);

        if (controller != null) {

            List<HandlerMethodInfo> handlerMethodInfos = handleMethodInfoMapping.get(requestMappingPath);

            try {
                if (handlerMethodInfos != null) {
                    String httpMethod = request.getMethod();
                    //遍历支持的Method
                    HandlerMethodInfo supportMethodInfo = null;
                    for(HandlerMethodInfo handlerMethodInfo : handlerMethodInfos){
                        if (handlerMethodInfo.getSupportedHttpMethods().contains(httpMethod)) {
                            supportMethodInfo = handlerMethodInfo;
                            break;
                        }
                    }
                    // HTTP 方法不支持
                    if(supportMethodInfo == null){
                        response.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
                        return;
                    }
                    // 处理标记为PageController的请求
                    if (controller instanceof PageController) {
                        String viewPath = (String)supportMethodInfo.getHandlerMethod().invoke(controller, request, response);
                        // 页面请求 forward
                        // request -> RequestDispatcher forward
                        // RequestDispatcher requestDispatcher = request.getRequestDispatcher(viewPath);
                        // ServletContext -> RequestDispatcher forward
                        // ServletContext -> RequestDispatcher 必须以 "/" 开头
                        ServletContext servletContext = request.getServletContext();
                        if (!viewPath.startsWith("/")) {
                            viewPath = "/" + viewPath;
                        }
                        RequestDispatcher requestDispatcher = servletContext.getRequestDispatcher(viewPath);
                        requestDispatcher.forward(request, response);
                    }
                }
            } catch (Throwable throwable) {
                if (throwable.getCause() instanceof IOException) {
                    throw (IOException) throwable.getCause();
                } else {
                    throw new ServletException(throwable.getCause());
                }
            }
        }
    }

//    private void beforeInvoke(Method handleMethod, HttpServletRequest request, HttpServletResponse response) {
//
//        CacheControl cacheControl = handleMethod.getAnnotation(CacheControl.class);
//
//        Map<String, List<String>> headers = new LinkedHashMap<>();
//
//        if (cacheControl != null) {
//            CacheControlHeaderWriter writer = new CacheControlHeaderWriter();
//            writer.write(headers, cacheControl.value());
//        }
//    }
}
